// TODO: CODE REVIEW & CLEANUP!
//
// Test.cs
//
// Implements the Test class, which helps implement unit tests.
//
// Note: To help debug unit tests, add "DEBUG_UNIT_TESTS" to the condititional symbols in the
// project settings.
//

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Threading;

namespace HomeUX.Utilities
{

/// <summary>
/// Helper methods for implementing unit tests.
/// </summary>
///
public class Test
{
	/// <summary>
	/// Calls <r>Fail</r> if a given condition is true
	/// </summary>
    /// 
    /// <param name="condition">The condition to test.</param>
	///
	public static void FailIf(bool condition)
	{
		if (condition)
			HelpFail("Test failed", 2);
	}

	/// <summary>
	/// Calls <r>Fail</r> if two values are equal.
	/// </summary>
    /// 
    /// <typeparam name="T">The type of the two values.</typeparam>
    /// 
    /// <param name="value1">The first value.</param>
    /// 
    /// <param name="value2">The second value.</param>
    ///
	public static void FailIfEqual<T>(T value1, T value2)
	{
		if (value1.Equals(value2))
			HelpFail(String.Format( "Test failed ([{0}] == [{1}])", value1, value2), 2);
	}

	/// <summary>
	/// Calls <r>Fail</r> if two values are not equal.
	/// </summary>
    /// 
    /// <typeparam name="T">The type of the two values.</typeparam>
    /// 
    /// <param name="value1">The first value.</param>
    /// 
    /// <param name="value2">The second value.</param>
    ///
	public static void FailIfNotEqual<T>(T value1, T value2)
	{
		if (!value1.Equals(value2))
			HelpFail(String.Format( "Test failed ([{0}] != [{1}])", value1, value2), 2);
	}

	/// <summary>
	/// Assuming a test failed, display a message which, in debug builds, includes the location of
	/// the failure.  Then, exit the application.
	/// </summary>
	///
	public static void Fail()
	{
        HelpFail("Test failed", 2);
	}

    /// <summary>
    /// Calls <r>Fail</r> if a given action doesn't throw a specified exception.
    /// </summary>
    /// 
    /// <param name="action">The action to perform.</param>
    /// 
    /// <param name="exceptionType">The expected exception type.</param>
    ///
    public static void FailUnlessException(Action action, Type exceptionType)
    {
		FailUnlessException(action, exceptionType, null);
	}

    /// <summary>
    /// Calls <r>Fail</r> if a given action doesn't throw a specified exception with exception
	/// text containing a given string.
    /// </summary>
    /// 
    /// <param name="action">The action to perform.</param>
    /// 
    /// <param name="exceptionType">The expected exception type.</param>
    /// 
    /// <param name="exceptionPattern">A regular expression that the exception message is expected
	/// 	to match, or null if no specific exception text is expected.</param>
    ///
    public static void FailUnlessException(Action action, Type exceptionType,
		string exceptionPattern)
    {
        try
        {
            action();
            HelpFail("Test failed (missing exception)", 2);
        }
        catch (Exception ex)
        {
            if (ex.GetType() != exceptionType)
                HelpFail(String.Format("Test failed (wrong exception: {0})", ex.GetType()), 2);
			else
			if (exceptionPattern != null)
			{
				string message = (ex.Message == null) ? "" : ex.Message;
				if (!Regex.Match(message, exceptionPattern,
						RegexOptions.Singleline).Success)
					HelpFail(String.Format( "Test failed (wrong text: \"{0}\")", message), 2);
			}
        }
    }

    /// <summary>
    /// Helps implement <r>Fail</r> and associated methods.
    /// </summary>
	///
    /// <param name="message">The message to display.</param>
    /// 
    /// <param name="skipFrames">The value of <c>skipFrames</c> to pass to the <n>StackFrame</n>
	/// 	constructor.</param>
    ///
	private static void HelpFail(string message, int skipFrames)
	{
		// display information about the failed test
		string wholeMessage;
		StackFrame stackFrame = new StackFrame(skipFrames, true);
		string fileName = stackFrame.GetFileName();
		if (fileName == null)
		{
			// no debug information available (e.g. .pdb file missing)
			wholeMessage = String.Format("{0} (no debug info)", message);
		}
		else
		{
			// debug information available
			int lineNumber = stackFrame.GetFileLineNumber();
			wholeMessage = String.Format("{0}: {1}({2})", message, fileName,
				lineNumber);
		}
		Console.WriteLine(wholeMessage);
        System.Diagnostics.Trace.WriteLine(wholeMessage);

		// exit the application
		Environment.Exit(1);
	}

	/// <summary>
	/// Waits for an asynchronous test to complete.
	/// </summary>
	///
	/// <param name="waitHandle">The test code (presumably running on another thread) will call
	/// 	<n>Set</n> on this <r>WaitHandle</r> when the test completes successfully.  If the test
	/// 	fails, the test code will call <r>Fail</r> (or a variant), which will end the
	/// 	application.</param>
	///
	/// <param name="timeout">After this many milliseconds of waiting, this method will stop
	/// 	waiting and perform the same action as if <r>Fail</r> had been called instead of this
	/// 	method.  Note that if <c>DEBUG_UNIT_TESTS</c> is #define'd then <r>Timeout.Infinite</r>
	/// 	is used instead of <pr>timeout</pr>.</param>
	///
	public static void WaitForTestToComplete(WaitHandle waitHandle, int timeout)
	{
		if (!waitHandle.WaitOne(
#if DEBUG_UNIT_TESTS
				Timeout.Infinite,
#else
				timeout,
#endif
				false))
			HelpFail("Test timed out", 2);
	}

    /// <summary>
    /// Formats unit test output to log as trace output, providing the conditional compilation
	/// symbol "DEBUG_UNIT_TESTS" is defined.
    /// </summary>
	///
    /// <param name="format">The format string.</param>
	///
    /// <param name="args">Formatting arguments.</param>
	///
	/// <remarks>
	/// <pr>format</pr> and <pr>args</pr> are used in the same was as in <n>String.Format</n>.
	/// </remarks>
	///
    [Conditional("DEBUG_UNIT_TESTS")]
	public static void Trace(string format, params object[] args)
	{
        System.Diagnostics.Trace.WriteLine(string.Format(format, args));
	}
}

}

